Skip to content

Note

Click here to download the full example code

intrinsic coregionalization

an intrinsic coregionalization is the simplest multivariate variogram model: every direct and cross variogram has the same normalized shape (one set of structures, ranges and anisotropy) scaled by one entry of a covariance matrix. the correlation between two variables is then the same at every lag. Coregionalization.fit(..., intrinsic=True) fits the shape to the pooled direct variograms, each divided by its sill, then fits the positive semi-definite covariance matrix. Coregionalization.intrinsic builds one from a variogram and a matrix. either way the result is an ordinary Coregionalization, so cokriging and cosimulation take it unchanged.

import sys
from pathlib import Path

HERE = Path(__file__).parent if "__file__" in globals() else Path.cwd()
sys.path.insert(0, str(HERE.parents[1]))
import boitata as bt
import matplotlib.pyplot as plt
import numpy as np
from common import ACCENT, GRAY, INK, save

jura = bt.datasets.jura()
train, test = jura["prediction"], jura["validation"]
xy = train.coords
cd, zn = train["Cd"], train["Zn"]
lag, max_lag = 0.1, 1.5
print(f"{len(cd)} samples, corr(Cd, Zn) {np.corrcoef(cd, zn)[0, 1]:.2f}")

Out:

259 samples, corr(Cd, Zn) 0.67

Cd and Zn

the data are the jura soil samples of coregionalization (mg/kg, coordinates in km). experimental_variograms computes both variograms and the cross-variogram as a VariogramSet, which the fit takes as it is. the intrinsic model has one shape (a nugget and two spherical structures) and one 2 x 2 sill matrix.

experimentals = bt.experimental_variograms(train, ["Cd", "Zn"], lag, max_lag)
icm = bt.Coregionalization.fit(experimentals, ["spherical", "spherical"], intrinsic=True)
lmc = bt.Coregionalization.fit(experimentals, ["spherical", "spherical"])


def sill(model):
    return model.nugget + sum(s for _, _, s in model.structures)


for name, model in [("intrinsic", icm), ("full LMC", lmc)]:
    c = sill(model)
    parts = ", ".join(
        f"{label} {100 * s[0, 0] / c[0, 0]:.0f} / {100 * s[1, 1] / c[1, 1]:.0f} %"
        for label, s in [("nugget", model.nugget), *((f"{m} {a:.2f} km", s) for m, a, s in model.structures)]
    )
    print(
        f"{name}: sills Cd {c[0, 0]:.3f}, Zn {c[1, 1]:.0f}, correlation {c[0, 1] / np.sqrt(c[0, 0] * c[1, 1]):.2f}"
    )
    print(f"  share of the Cd / Zn sill: {parts}")

Out:

intrinsic: sills Cd 0.863, Zn 899, correlation 0.64
  share of the Cd / Zn sill: nugget 0 / 0 %, spherical 0.16 km 68 / 68 %, spherical 1.54 km 32 / 32 %
full LMC: sills Cd 0.828, Zn 923, correlation 0.64
  share of the Cd / Zn sill: nugget 0 / 0 %, spherical 0.15 km 83 / 53 %, spherical 1.47 km 17 / 47 %

the full linear model of coregionalization of coregionalization gives each variable its own mix of the shared structures: Cd puts 83 % of its sill in the short structure, Zn 53 %. the intrinsic model gives both the same 68 %, a compromise, and one correlation, 0.64, at every lag. the total sills and the correlation differ little between the two models, so the curves meet at large lags. at short lags the intrinsic model is too smooth for Cd and too rough for Zn:

h = np.linspace(0, max_lag, 101)[1:]
origin = np.zeros((h.size, 3))
away = np.c_[h, np.zeros((h.size, 2))]
panels = ((0, 0, "Cd"), (1, 1, "Zn"), (0, 1, "Cd × Zn"))
fig, axes = plt.subplots(1, 3, figsize=(10, 3.2), layout="constrained")
for ax, (i, j, name) in zip(axes, panels):
    bt.plot.variogram(experimentals[i, j], ax=ax, color=GRAY)
    for model, color, label in [(icm, ACCENT, "intrinsic"), (lmc, INK, "full LMC")]:
        gamma = model.cross_covariance(i, j, origin, origin) - model.cross_covariance(i, j, origin, away)
        ax.plot(h, gamma, color=color, lw=1, label=label)
    ax.set(title=name, xlabel="Lag (km)", ylabel="γ(h)" if i == j else "γ₁₂(h)")
axes[0].legend()
save(fig, "variograms")

Cd, Zn, Cd × Zn

cokriging with either model

both models go to Cokriging the same way. the test kriges Cd at the 100 validation samples by collocated cokriging, with Zn known there, as in cokriging. with correlations this close, the simpler model predicts as well:

search = bt.Search(radius=1.5, max_samples=24, min_samples=4)
truth = test["Cd"]
for name, model in [("intrinsic", icm), ("full LMC", lmc)]:
    ck = bt.Cokriging(model, search, means=[cd.mean(), zn.mean()])
    ck.fit(np.vstack([xy, xy]), np.r_[cd, zn], [0] * len(cd) + [1] * len(zn))
    estimate = ck.predict(test, collocated={1: test["Zn"]})
    print(f"{name:>10}: validation RMSE {bt.compare(estimate, truth)['rmse']:.3f} mg/kg")

Out:

 intrinsic: validation RMSE 0.681 mg/kg
  full LMC: validation RMSE 0.689 mg/kg

seven metals

the intrinsic model grows with the number of variables only through its covariance matrix: seven metals need one shape and 28 matrix entries, fitted in one pass. its correlations hold at every lag:

metals = ["Cd", "Co", "Cr", "Cu", "Ni", "Pb", "Zn"]
seven = bt.Coregionalization.fit(
    bt.experimental_variograms(train, metals, lag, max_lag), ["spherical", "spherical"], intrinsic=True
)
c = sill(seven)
corr = c / np.sqrt(np.outer(np.diag(c), np.diag(c)))
print(f"smallest eigenvalue of the sill matrix {np.linalg.eigvalsh(c)[0]:.3g}")
print("      " + "".join(f"{m:>6}" for m in metals))
for m, row in zip(metals, corr):
    print(f"{m:<6}" + "".join(f"{r:6.2f}" for r in row))

Out:

smallest eigenvalue of the sill matrix 0.375
          Cd    Co    Cr    Cu    Ni    Pb    Zn
Cd      1.00  0.26  0.62  0.11  0.50  0.25  0.64
Co      0.26  1.00  0.52  0.30  0.74  0.24  0.53
Cr      0.62  0.52  1.00  0.23  0.73  0.31  0.69
Cu      0.11  0.30  0.23  1.00  0.28  0.79  0.60
Ni      0.50  0.74  0.73  0.28  1.00  0.36  0.67
Pb      0.25  0.24  0.31  0.79  0.36  1.00  0.63
Zn      0.64  0.53  0.69  0.60  0.67  0.63  1.00

Total running time of the script: ( 0 minutes 0.282 seconds)

Download Python source code: example_05_08.py

Download Jupyter notebook: example_05_08.ipynb

Gallery generated by mkdocs-gallery